﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using MSNGame1;
using System.Windows.Browser;
using System.IO;
using System.Text;

namespace Subattle
{
    public partial class Page : UserControl
    {
        enum GameState { Ingame, Menu }

        int wins = 0, losses = 0;
        bool isWinner = false;
        int health = 100, enemyHealth = 100;

        BubbleParticles bubbles;
        TorpedoHandler torpedoHandler;
        DateTime lastTick = DateTime.Now;
        GameState gameState = GameState.Menu;
        Random random = new Random();

        string strStart = string.Format("Klik for at starte spillet!");
        string strWaitForHostToStart = "Venter på at {0} starter spillet...";
        string strWaitingFor = "Venter på {0}...";
        string strClickWhenReady = "Tryk når du er klar!";
        string strDisconnected = "Forbindelsen blev afbrudt! :(";
        string strInTheLead = "Du fører med {0}-{1}";
        string strLosing = "{0} fører med {1}-{2}";
        string strTied = "I står lige {0}-{1}";
        string strYouWin = "Du Vandt!";
        string strYouLose = "Du Tabte!";
        string strClickToPlayAgain = "Klik for at spille igen!";

        public Page() : base()
        {
            InitializeComponent();
            
            // Initialize the bubble particle system
            bubbles = new BubbleParticles(200, LayoutRoot);
            torpedoHandler = new TorpedoHandler(LayoutRoot, bubbles);

            // Handle when the game goes fullscreen
            Application.Current.Host.Content.FullScreenChanged += new EventHandler(Application_FullScreenChanged);
            
            // We want to be able to scale the entire game to fill the fullscreen correctly, so we add a scaletransform            
            this.RenderTransform = new ScaleTransform() { ScaleX = 1, ScaleY = 1 };
            
            // 
            pingWave.RenderTransform = new RotateTransform();
            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);

            // Start Networking
            InitializeNetworking();

//            gameLoop.Begin();
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            TimeSpan elapsed = DateTime.Now - lastTick;
            lastTick = DateTime.Now;

            switch (gameState)
            {
                case GameState.Ingame:
                    // fade out the first screen
                    if (Menu.Opacity > 0)
                        Menu.Opacity = Math.Max(0, Menu.Opacity - elapsed.TotalSeconds);

                    torpedoHandler.Update(elapsed);
                    
                    UpdateSubs(elapsed);
                    UpdatePing(elapsed);

                    // Update the network
                    NetUpdate(elapsed);

                    // The game is over when:
                    //   someone has no more health 
                    //   and the remote client has received all our fired and hit torpedos
                    if ((health <= 0 || enemyHealth <= 0)
                        && firedTorpedos.Count == 0 && netTorpedoImpacts.Count == 0)
                    {
                        GameEnded();
                    }

                    break;
                case GameState.Menu:
                    if (Menu.Opacity < 1)
                        Menu.Opacity = Math.Min(1, Menu.Opacity + elapsed.TotalSeconds);
                    
                    if (Sub.Opacity >= 0)
                        Sub.Opacity = Math.Max(0, Sub.Opacity - elapsed.TotalSeconds);

                    if (Enemy.Opacity >= 0)
                        Enemy.Opacity = Math.Max(0, Enemy.Opacity - elapsed.TotalSeconds);
                    break;
            }

            UpdateHealthBars(elapsed);
            bubbles.Update(elapsed);

            // Restart the StoryBoard
            //gameLoop.Begin();
        }

        void UpdateHealthBars(TimeSpan elapsed)
        {
            // Fade out the healthbars
            if (EnemyHealth.Opacity > 0)
                EnemyHealth.Opacity = Math.Max(0, EnemyHealth.Opacity - elapsed.TotalSeconds / 2.0);

            // Get the enemy health from 0-1
            double enemyHealthOffset = (double)enemyHealth / 100.0;

            // if enemy bar does not reflect its health, then update it
            if (EnemyHealthMax.Offset > enemyHealthOffset)
            {
                EnemyHealthMax.Offset = Math.Max(enemyHealthOffset, EnemyHealthMax.Offset - elapsed.TotalSeconds);
                EnemyHealthAlmostMax.Offset = EnemyHealthMax.Offset - 0.001;
            }

            // Fade out the bar
            if (SubHealth.Opacity > 0)
                SubHealth.Opacity = Math.Max(0, SubHealth.Opacity - elapsed.TotalSeconds / 2.0);

            // Get the enemy health from 0-1    
            double subHealthOffset = ((double)health) / 100.0;

            // if enemy bar does not reflect its health, then update it
            if (SubHealthMax.Offset > subHealthOffset)
            {
                SubHealthMax.Offset = Math.Max(subHealthOffset, SubHealthMax.Offset - elapsed.TotalSeconds);
                SubHealthAlmostMax.Offset = SubHealthMax.Offset - 0.001;
            }

        }
        
        void UpdateSubs(TimeSpan elapsed)
        {
            // Move our sub
            Sub.Update(elapsed);

            // Move enemy sub
            Enemy.Update(elapsed);

            // handle torpedo collisions
            foreach (var impact in torpedoHandler.Collision(Enemy))
            {
                // Send the impact to the other player
                netTorpedoImpacts.Enqueue(impact);

                // Handle the impact on our side
                EnemySubHit(impact.Position);
            }

        }

        void EnemySubHit(Vector2 position)
        {
            // Decrease Health
            enemyHealth -= 35;

            // Display health
            EnemyHealth.Opacity = 1;
            
            // Play the distant explosion sound 
            soundDistantExplosion.Stop(); 
            soundDistantExplosion.Play();

            // Show the explosion
            ShowExplosion(position);
        }

        void MySubHit(Vector2 position)
        {
            // Decrease Health
            health -= 35;

            // Display health
            SubHealth.Opacity = 1;

            // Play explosion sound
            soundExplosion.Stop(); 
            soundExplosion.Play();

            // Show the explosion
            ShowExplosion(position);
        }

        void ShowExplosion(Vector2 position)
        {
            // create a new explosion
            Explosion exp = new Explosion();
            
            // add it to our gui
            LayoutRoot.Children.Add(exp);
            
            // at the correct position
            Canvas.SetLeft(exp, position.X - 37.5);
            Canvas.SetTop(exp, position.Y - 37.5);
            
            // Spawn a bunch of bubbles around the explosion
            for (int i = 0; i < 50; i++)
            {
                // Randomize their trajectory
                Vector2 newDirection = new Vector2((float)(random.NextDouble() - .5), (float)(random.NextDouble() - .5));
                newDirection.Normalize();
                newDirection *= (float)random.NextDouble() * 35f;
                
                Vector2 newPosition = position + newDirection;
                bubbles.SpawnBubble(newPosition, (newDirection));
            }
        }

        /// <summary>
        /// Resets the game and begins a new one.
        /// </summary>
        void BeginNewGame()
        {
            health = 100;
            enemyHealth = 100;

            // reset health bars!
            SubHealthMax.Offset = 1;
            SubHealthAlmostMax.Offset = SubHealthMax.Offset - 0.001;

            EnemyHealthMax.Offset = 1;
            EnemyHealthAlmostMax.Offset = SubHealthMax.Offset - 0.001;

            torpedoHandler.Reset();

            Sub.Visibility = Enemy.Visibility = Visibility.Visible;
            Sub.Opacity = 1;
            Enemy.Opacity = 0;
            gameState = GameState.Ingame;
        }

        /// <summary>
        /// Ends the game and displays the winner. Returns game to menu so a new game can be started 
        /// </summary>
        private void GameEnded()
        {
            gameState = GameState.Menu;
            if (health <= 0)
            {
                isWinner = false;
                losses++;
            }

            if (enemyHealth <= 0)
            {
                isWinner = true;
                wins++;
            }
            clientReady = false;


            Score.Visibility = Visibility.Visible;
            if(wins > losses) 
            {
                Score.Text = string.Format(strInTheLead, wins, losses);
            }
            else if (wins < losses)
            {
                Score.Text = string.Format(strLosing, msn.Users.RemoteUser.Name, losses, wins);
            }
            else
            {
                Score.Text = string.Format(strTied, wins, losses);
            }
            string winner = isWinner ? strYouWin : strYouLose;
            if (msn.IsHost)
            {
                PulseSize.Stop();
                StatusText.Text = winner;
            }
            else
            {
                PulseSize.Begin();
                StatusText.Text = winner + Environment.NewLine + Environment.NewLine + strClickToPlayAgain;
            }
        }

        #region Events

        private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            switch (gameState)
            {
                case GameState.Ingame:
                    Point p = e.GetPosition(LayoutRoot);
                    Sub.Target = new Vector2((float)p.X, (float)p.Y);
                    break;
                case GameState.Menu:
                    if (remoteClientLoaded)
                    {
                        SendStartGame();
                    }
                    break;
                default:
                    break;
            }
        }

        void Application_FullScreenChanged(object sender, EventArgs e)
        {
            ((ScaleTransform)this.RenderTransform).ScaleX = Application.Current.Host.Content.IsFullScreen ? Application.Current.Host.Content.ActualWidth / Width : 1;
            ((ScaleTransform)this.RenderTransform).ScaleY = Application.Current.Host.Content.IsFullScreen ? Application.Current.Host.Content.ActualHeight / Height : 1;
        }

        void soundAmbient_MediaEnded(object sender, RoutedEventArgs e)
        {
            // Loop our background sound
            soundAmbient.Stop();
            soundAmbient.Play();
        }

        void Page_KeyDown(object sender, KeyEventArgs e)
        {
            switch (gameState)
            {
                case GameState.Ingame:
                    if (e.Key == Key.Space && torpedoHandler.CanFireTorpedo)
                    {
                        soundTorpedo.Stop();
                        soundTorpedo.Play();

                        Torpedo newTorpedo = torpedoHandler.FireTorpedo(Sub);
                        firedTorpedos.Enqueue(newTorpedo);
                    }

                    if (e.Key == Key.F)
                    {
                        Application.Current.Host.Content.IsFullScreen = !Application.Current.Host.Content.IsFullScreen;
                    }
                    break;
                case GameState.Menu:
                    if (remoteClientLoaded)
                    {
                        SendStartGame();
                    }
                    break;
                default:
                    break;
            }

        }

        #endregion

        Vector2 GenerateRandomPosition()
        {
            return new Vector2((float)(10 + random.NextDouble() * (Width - 20.0)), (float)(10.0 + random.NextDouble() * (Height - 20.0)));
        }
    }

}
